﻿using System;
using System.Collections.Generic;
using Black.Surface2DEngine.Common.Args;

namespace Black.Surface2DEngine.Common
{
    /// <summary>
    /// Classe de gestion d'un timer pour un écran
    /// </summary>
    public class Timer: IDisposable
    {
        #region Events

        public event EventHandler<TimerTickArgs> Tick;

        #endregion

        #region Variables d'instances

        private float m_fCurrentTick;
        private List<TimerItem> m_oTicksBefore;
        private List<TimerItem> m_oTicksAfter;

        #endregion

        #region Constructeur et destructeur

        /// <summary>
        /// Constructeur de la classe
        /// </summary>
        public Timer()
        {
            this.m_fCurrentTick = 0.0f;
            this.m_oTicksBefore = new List<TimerItem>();
            this.m_oTicksAfter = new List<TimerItem>();
        }

        /// <summary>
        /// Libère les ressources
        /// </summary>
        public void Dispose()
        {
            this.Clear();

            this.m_oTicksBefore = null;
            this.m_oTicksAfter = null;
        }

        #endregion

        #region Fonctions publiques

        /// <summary>
        /// Ajoute une référence tick au Timer
        /// </summary>
        /// <param name="p_fTick">Indicateur de temps du tick</param>
        /// <param name="p_bRelative">TRUE l'indicateur de temps est ajouté au compteur tick actuel, FALSE l'indicteur de temps est absolue</param>
        /// <param name="p_fLoopTick">Temps entre le tick et la boucle du prochain tick, NULL si pas de boucle</param>
        /// <param name="p_bAfter">TRUE le tick sera exécuté après l'update écran, FALSE avant</param>
        public void AddTick(float p_fTick, bool p_bRelative, float? p_fLoopTick, float p_fLoopCount, bool p_bAfter)
        {
            this.AddTick(p_fTick, null, null, p_bRelative, p_fLoopTick, p_fLoopCount, p_bAfter);
        }

        /// <summary>
        /// Ajoute une référence tick au Timer
        /// </summary>
        /// <param name="p_fTick">Indicateur de temps du tick</param>
        /// <param name="p_oCallBack">Methode de rappel lors du tick</param>
        /// <param name="p_bRelative">TRUE l'indicateur de temps est ajouté au compteur tick actuel, FALSE l'indicteur de temps est absolue</param>
        /// <param name="p_fLoopTick">Temps entre le tick et la boucle du prochain tick, NULL si pas de boucle</param>
        /// <param name="p_fLoopCount">Nombre de tick a faire avec le boucle, 0 si pas de boucle ou boucle infinie</param>
        /// <param name="p_bAfter">TRUE le tick sera exécuté après l'update écran, FALSE avant</param>
        public void AddTick(float p_fTick, EventHandler<TimerCallBackArgs> p_oCallBack, bool p_bRelative, float? p_fLoopTick, float p_fLoopCount, bool p_bAfter)
        {
            this.AddTick(p_fTick, p_oCallBack, null, p_bRelative, p_fLoopTick, p_fLoopCount, p_bAfter);
        }

        /// <summary>
        /// Ajoute une référence tick au Timer
        /// </summary>
        /// <param name="p_fTick">Indicateur de temps du tick</param>
        /// <param name="p_oParams">Liste optionnelle de paramètres</param>
        /// <param name="p_bRelative">TRUE l'indicateur de temps est ajouté au compteur tick actuel, FALSE l'indicteur de temps est absolue</param>
        /// <param name="p_fLoopTick">Temps entre le tick et la boucle du prochain tick, NULL si pas de boucle</param>
        /// <param name="p_fLoopCount">Nombre de tick a faire avec le boucle, 0 si pas de boucle ou boucle infinie</param>
        /// <param name="p_bAfter">TRUE le tick sera exécuté après l'update écran, FALSE avant</param>
        public void AddTick(float p_fTick, Dictionary<string, Object> p_oParams, bool p_bRelative, float? p_fLoopTick, float p_fLoopCount, bool p_bAfter)
        {
            this.AddTick(p_fTick, null, p_oParams, p_bRelative, p_fLoopTick, p_fLoopCount, p_bAfter);
        }

        /// <summary>
        /// Ajoute une référence tick au Timer
        /// </summary>
        /// <param name="p_fTick">Indicateur de temps du tick</param>
        /// <param name="p_oCallBack">Methode de rappel lors du tick</param>
        /// <param name="p_oParams">Liste optionnelle de paramètres</param>
        /// <param name="p_bRelative">TRUE l'indicateur de temps est ajouté au compteur tick actuel, FALSE l'indicteur de temps est absolue</param>
        /// <param name="p_fLoopTick">Temps entre le tick et la boucle du prochain tick, NULL si pas de boucle</param>
        /// <param name="p_fLoopCount">Nombre de tick a faire avec le boucle, 0 si pas de boucle ou boucle infinie</param>
        /// <param name="p_bAfter">TRUE le tick sera exécuté après l'update écran, FALSE avant</param>
        public void AddTick(float p_fTick, EventHandler<TimerCallBackArgs> p_oCallBack, Dictionary<string, Object> p_oParams, bool p_bRelative, float? p_fLoopTick, float p_fLoopCount, bool p_bAfter)
        {
            TimerItem oItem = null;

            // Création de l'objet

            if ( p_bRelative )
                oItem = new TimerItem(this.m_fCurrentTick + p_fTick, p_oCallBack);
            else
                oItem = new TimerItem(p_fTick, p_oCallBack);

            oItem.Loop = p_fLoopTick;
            oItem.LoopMax = p_fLoopCount;

            // Transfert des paramètres

            if ( p_oParams != null )
            {
                foreach ( KeyValuePair<string, object> oPair in p_oParams )
                    oItem.AddParam(oPair.Key, oPair.Value);
            }

            // Ajout à la liste

            this.AddTick(oItem, p_bAfter);
        }

        /// <summary>
        /// Ajoute une référence tick au Timer
        /// </summary>
        /// <param name="p_oTick">Définition du tick</param>
        /// <param name="p_bAfter">TRUE le tick sera exécuté après l'update écran, FALSE avant</param>
        public void AddTick(TimerItem p_oTick, bool p_bAfter)
        {
            if ( p_bAfter )
            {
                this.m_oTicksAfter.Add(p_oTick);
                this.m_oTicksAfter.Sort();
            }
            else
            {
                this.m_oTicksBefore.Add(p_oTick);
                this.m_oTicksBefore.Sort();
            }
        }

        /// <summary>
        /// Efface les ticks programmés
        /// </summary>
        public void Clear()
        {
            foreach ( TimerItem oItem in this.m_oTicksBefore )
                oItem.Dispose();

            foreach ( TimerItem oItem in this.m_oTicksAfter )
                oItem.Dispose();

            this.m_oTicksBefore.Clear();
            this.m_oTicksAfter.Clear();
        }

        /// <summary>
        /// Met à jour la logique du timer
        /// </summary>
        /// <param name="p_fTime">Indicateur temporel</param>
        /// <param name="p_bAfter">TRUE traite les ticks AFTER, FALSE sinon</param>
        public void Update(float p_fTime, bool p_bAfter)
        {
            bool bCancel = false;
            bool bMasterTick = false;
            List<TimerItem> oRef = null;

            // Sauvegarde du tick courant pour les AddTick

            this.m_fCurrentTick = p_fTime;

            // On récupère la liste de référence

            if ( p_bAfter )
                oRef = this.m_oTicksAfter;
            else
                oRef = this.m_oTicksBefore;

            // Vérification des ticks

            for ( int i = 0; i < oRef.Count; i++ )
            {
                if ( oRef[i].Tick > p_fTime )
                    break;

                // Traitement du tick

                bCancel = oRef[i].Raise(p_fTime, p_bAfter);
                bMasterTick = true;

                // Loop ?

                if ( !bCancel && oRef[i].Loop != null )
                    oRef[i].Tick += oRef[i].Loop.Value;
                else
                {
                    // On le retire

                    oRef[i].Dispose();
                    oRef.RemoveAt(i);
                    i--;
                }
            }

            // Gestion du master tick

            if ( bMasterTick && this.Tick != null )
                this.Tick(this, new TimerTickArgs(p_fTime, p_bAfter));
        }

        #endregion
    }
}
